package com.jie.gateway.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.jie.common.constant.RedisKey;
import com.jie.common.constant.StrConst;
import com.jie.common.enums.ApiRuleType;
import com.jie.common.enums.RuleTimeUnit;
import com.jie.common.util.BaseParameterHolder;
import com.jie.component.exception.base.BusinessException;
import com.jie.component.exception.enums.BusinessError;
import com.jie.gateway.property.GatewayProperty;
import com.jie.model.redis.gateway.ApiRestrictData;
import com.jie.model.vo.gateway.DepthRuleVO;
import com.jie.model.vo.gateway.RuleVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Service;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author jie
 * @version 1.0 2025-02-16
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class ApiRestrictService {

    private final GatewayProperty gatewayProperty;

    private final StringRedisTemplate stringRedisTemplate;

    private final ApiRestrictCacheOperate apiRestrictCacheOperate;

    /**
     * 请求路径是否需要限流
     * @param requestUri
     * @return
     */
    public boolean needApiRestrict(String requestUri) {
        if (gatewayProperty.getApiRestrictPaths() != null) {
            for (String apiRestrictPath : gatewayProperty.getApiRestrictPaths()) {
                PathMatcher matcher = new AntPathMatcher();
                if(matcher.match(apiRestrictPath, requestUri)){
                    return true;
                }
            }
        }
        return false;
    }

    public void doApiRestrict(String url, ServerHttpRequest request) {
        if (!needApiRestrict(url)) {
            return;
        }

        log.info("执行请求限流 {}", request.getPath().value());
        long triggerResult = 0L; // 结果，1表示触发限制
        long triggerCallStat = 0L; // 统计类型
        long apiCount; // API调用次数
        long threshold; // 阈值
        long messageIndex; // 消息索引
        String message = ""; // 限制提示消息

        // 获取请求IP
        String ip = getIpAddress(request);

        // 构建限制key：IP_用户ID_URL
        StringBuilder stringBuilder = new StringBuilder(ip);
        String userId = BaseParameterHolder.getParameter(StrConst.USER_ID);
        if (StrUtil.isNotBlank(userId)) {
            stringBuilder.append("_").append(userId);
        }
        String commonKey = stringBuilder.append("_").append(url).toString();

        try {
            List<DepthRuleVO> depthRuleVOList = null;
            RuleVO ruleVO = null;

            // 获取基础规则
            String json = (String) stringRedisTemplate.opsForHash().get(RedisKey.getRedisKey(RedisKey.ALL_RULE_HASH), RedisKey.getRedisKey(RedisKey.RULE));
            if (StrUtil.isNotBlank(json)) {
                ruleVO = JSONUtil.toBean(json, RuleVO.class);
            }

            // 获取深度规则
            json = (String) stringRedisTemplate.opsForHash().get(RedisKey.getRedisKey(RedisKey.ALL_RULE_HASH), RedisKey.getRedisKey(RedisKey.DEPTH_RULE));
            if (StrUtil.isNotBlank(json)) {
                depthRuleVOList = JSONUtil.parseArray(json).toList(DepthRuleVO.class);
            }

            // 确定API规则类型
            int apiRuleType = ApiRuleType.NO_RULE.getCode();
            if (ruleVO != null) {
                apiRuleType = ApiRuleType.RULE.getCode();
                message = ruleVO.getMessage();
            }
            if (ruleVO != null && CollUtil.isNotEmpty(depthRuleVOList)) {
                apiRuleType = ApiRuleType.DEPTH_RULE.getCode();
            }

            // 执行规则检查
            if (apiRuleType == ApiRuleType.RULE.getCode() || apiRuleType == ApiRuleType.DEPTH_RULE.getCode()) {
                JSONObject parameter = getRuleParameter(apiRuleType, commonKey, ruleVO);

                // 如果是深度规则，添加深度规则参数
                if (apiRuleType == ApiRuleType.DEPTH_RULE.getCode()) {
                    parameter = getDepthRuleParameter(parameter, commonKey, depthRuleVOList);
                }

                // 执行规则检查
                ApiRestrictData apiRestrictData = apiRestrictCacheOperate
                        .apiRuleOperate(Collections.singletonList(JSONUtil.toJsonStr(parameter)), new Object[]{});

                // 获取检查结果
                triggerResult = apiRestrictData.getTriggerResult();
                triggerCallStat = apiRestrictData.getTriggerCallStat();
                apiCount = apiRestrictData.getApiCount();
                threshold = apiRestrictData.getThreshold();
                messageIndex = apiRestrictData.getMessageIndex();

                // 获取提示信息
                if (messageIndex != -1) {
                    message = Optional.ofNullable(depthRuleVOList.get((int)messageIndex))
                            .map(DepthRuleVO::getMessage)
                            .filter(StrUtil::isNotBlank)
                            .orElse(message);
                }

                log.info("api rule [key : {}], [triggerResult : {}], [triggerCallStat : {}], [apiCount : {}], [threshold : {}]",
                        commonKey, triggerResult, triggerCallStat, apiCount, threshold);
            }

        } catch (Exception e) {
            log.error("redis lua error", e);
        }

        // 如果出发了限制规则
        if (triggerResult == 1) {
            // 保存API调用数据
            if (triggerCallStat == ApiRuleType.RULE.getCode() || triggerCallStat == ApiRuleType.DEPTH_RULE.getCode()) {
                saveApiData(request, url, (int) triggerResult);
            }
            // 抛出限制异常
            String defaultMessage = BusinessError.API_RULE_TRIGGER.getMsg();
            if (StrUtil.isNotBlank(message)) {
                defaultMessage = message;
            }
            throw new BusinessException(BusinessError.API_RULE_TRIGGER.getCode(), defaultMessage);
        }
    }

    /**
     * 获取基础规则的参数
     * @param apiRuleType
     * @param commonKey
     * @param ruleVO
     * @return
     */
    public JSONObject getRuleParameter(int apiRuleType, String commonKey, RuleVO ruleVO) {
        JSONObject parameter = new JSONObject();
        // 设置规则类型
        parameter.put("apiRuleType", apiRuleType);
        // 设置规则key
        String ruleKey = "rule_api_limit" + "_" + commonKey;
        parameter.put("ruleKey", ruleKey);
        // 设置统计时间
        parameter.put("statTime", String.valueOf(
                Objects.equals(ruleVO.getStatTimeType(), RuleTimeUnit.SECOND.getCode()) ?
                        ruleVO.getStatTime() : ruleVO.getStatTime() * 60));
        // 设置阈值
        parameter.put("threshold", ruleVO.getThreshold());
        // 设置生效时间
        parameter.put("effectiveTime", String.valueOf(
                Objects.equals(ruleVO.getEffectiveTimeType(), RuleTimeUnit.SECOND.getCode()) ?
                        ruleVO.getEffectiveTime() : ruleVO.getEffectiveTime() * 60));
        // 设置限制key
        parameter.put("ruleLimitKey", RedisKey.getRedisKey(RedisKey.RULE_LIMIT + commonKey));
        // 设置统计的key
        parameter.put("zSetRuleStatKey", RedisKey.getRedisKey(RedisKey.Z_SET_RULE_STAT + commonKey));
        return parameter;
    }

    /**
     * 获取深度规则参数
     * @param parameter
     * @param commonKey
     * @param depthRuleVOList
     * @return
     */
    public JSONObject getDepthRuleParameter(JSONObject parameter, String commonKey, List<DepthRuleVO> depthRuleVOList) {
        // 按照开始时间窗口排序
        depthRuleVOList = sortStartTimeWindow(depthRuleVOList);
        // 设置深度规则数量
        parameter.put("depthRuleSize", String.valueOf(depthRuleVOList.size()));
        // 设置当前时间
        parameter.put("currentTime", System.currentTimeMillis());
        // 构建深度规则列表
        List<JSONObject> depthRules = new ArrayList<>();
        for (int i = 0; i < depthRuleVOList.size(); i++) {
            JSONObject depthRule = new JSONObject();
            DepthRuleVO depthRuleVO = depthRuleVOList.get(i);

            // 设置统计时间
            depthRule.put("statTime",
                    Objects.equals(depthRuleVO.getStatTimeType(), RuleTimeUnit.SECOND.getCode()) ?
                            depthRuleVO.getStatTime() : depthRuleVO.getStatTime() * 60);
            // 设置阈值
            depthRule.put("threshold", depthRuleVO.getThreshold());

            // 设置生效时间
            depthRule.put("effectiveTime", String.valueOf(
                    Objects.equals(depthRuleVO.getEffectiveTimeType(), RuleTimeUnit.SECOND.getCode()) ?
                            depthRuleVO.getEffectiveTime() : depthRuleVO.getEffectiveTime() * 60));

            // 设置深度规则限制key
            depthRule.put("depthRuleLimit",
                    RedisKey.getRedisKey(RedisKey.DEPTH_RULE_LIMIT + i + ":" + commonKey));

            // 设置时间窗口
            depthRule.put("startTimeWindowTimestamp", depthRuleVO.getStartTimeWindowTimestamp());
            depthRule.put("endTimeWindowTimestamp", depthRuleVO.getEndTimeWindowTimestamp());

            depthRules.add(depthRule);
        }

        parameter.put("depthRules", depthRules);
        return parameter;
    }

    /**
     * 对深度规则按照开始时间窗口排序
     * @param depthRuleVOList
     * @return
     */
    public List<DepthRuleVO> sortStartTimeWindow(List<DepthRuleVO> depthRuleVOList) {
        return depthRuleVOList.stream()
                .peek(depthRuleVO -> {
                    depthRuleVO.setStartTimeWindowTimestamp(
                            getTimeWindowTimestamp(depthRuleVO.getStartTimeWindow())
                    );
                    depthRuleVO.setEndTimeWindowTimestamp(
                            getTimeWindowTimestamp(depthRuleVO.getEndTimeWindow())
                    );
                })
                .sorted(Comparator.comparing(DepthRuleVO::getStartTimeWindowTimestamp))
                .collect(Collectors.toList());
    }

    /**
     * 获取时间窗口的时间戳
     * @param timeWindow
     * @return
     */
    public long getTimeWindowTimestamp(String timeWindow) {
        String today = DateUtil.today();
        return DateUtil.parse(today + " " + timeWindow).getTime();
    }

    public void saveApiData(ServerHttpRequest request, String apiUrl, Integer type){
        // TODO: kafka发送消息保存api调用记录
        //        ApiDataDto apiDataDto = new ApiDataDto();
//        apiDataDto.setId(uidGenerator.getUid());
//        apiDataDto.setApiAddress(getIpAddress(request));
//        apiDataDto.setApiUrl(apiUrl);
//        apiDataDto.setCreateTime(DateUtils.now());
//        apiDataDto.setCallDayTime(DateUtils.nowStr(DateUtils.FORMAT_DATE));
//        apiDataDto.setCallHourTime(DateUtils.nowStr(DateUtils.FORMAT_HOUR));
//        apiDataDto.setCallMinuteTime(DateUtils.nowStr(DateUtils.FORMAT_MINUTE));
//        apiDataDto.setCallSecondTime(DateUtils.nowStr(DateUtils.FORMAT_SECOND));
//        apiDataDto.setType(type);
//        // 如果消息发送服务存在，则发送消息
//        Optional.ofNullable(apiDataMessageSend)
//                .ifPresent(send -> send.sendMessage(JSON.toJSONString(apiDataDto)));
    }

    /**
     * 获取真实的ip地址
     * @param request
     * @return
     */
    public String getIpAddress(ServerHttpRequest request) {
        String unknown = "unknown";
        String split = ",";
        HttpHeaders headers = request.getHeaders();

        // 依次从不同的请求头中获取IP
        String ip = headers.getFirst("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !unknown.equalsIgnoreCase(ip)) {
            if (ip.contains(split)) {
                ip = ip.split(split)[0];  // 获取第一个IP（真实IP）
            }
        }
        // 依次尝试其他请求头
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = headers.getFirst("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = headers.getFirst("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = headers.getFirst("X-Real-IP");
        }
        // 如果都没有，则使用远程地址
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = Objects.requireNonNull(request.getRemoteAddress()).getAddress().getHostAddress();
        }
        return ip;
    }
}
